package ru.ifmo.filemanager.core;

import static java.lang.System.err;
import static java.lang.Thread.currentThread;
import static java.lang.Thread.setDefaultUncaughtExceptionHandler;
import static org.eclipse.swt.SWT.*;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Widget;

import ru.ifmo.filemanager.filesystem.FileSystem;

public class Core {

	private Core() {
	}

	private static final Display display;
	private static final Shell mainShell;
	private static final TabFolder tabs;
	private static Composite panel;

	static {
		try {
			Display disp = Display.getCurrent();
			if (disp == null) {
				disp = new Display();
			}
			display = disp;

			mainShell = new Shell(display, SHELL_TRIM);
			{
				GridLayout layout = new GridLayout();
				layout.marginWidth = 0;
				layout.marginHeight = 0;
				layout.horizontalSpacing = 0;
				layout.verticalSpacing = 0;
				mainShell.setLayout(layout);
			}

			tabs = new TabFolder(mainShell, 0);
			tabs.setLayoutData(new GridData(FILL, FILL, true, true));

		} catch (Throwable e) {
			err.println("Unable to initialize the GUI subsystem. "
					+ "Check that SWT library is present in class path.");
			err.println("The error was:");
			e.printStackTrace(err);
			System.exit(1);
			throw new Error();
		}
		setDefaultUncaughtExceptionHandler(new ExceptionHandler());
	}

	public static Shell getMainShell() {
		return mainShell;
	}

	public static void main() {
		mainShell.setText("File manager");

		Composite mainPanel = new Composite(tabs, 0);
		{
			mainPanel.setLayout(new GridLayout());
		}

		panel = new Composite(mainPanel, 0);
		{
			panel.setLayout(new RowLayout(HORIZONTAL));
			panel.setLayoutData(new GridData(FILL, FILL, true, false));
		}

		addTabControl(mainPanel, "Main");

		List files = new List(mainPanel, BORDER | SINGLE | V_SCROLL);
		files.setLayoutData(new GridData(FILL, FILL, true, true));

		FileSystem.setUpGUI(files);
		mainShell.addShellListener(new ShellAdapter() {

			public void shellClosed(ShellEvent e) {
				exit();
			}
		});
		mainShell.setMaximized(true);
		init();
		mainShell.setVisible(true);
		mainLoop();
	}

	static void init() {
		FileSystem.init();
		PluginLoader.loadPlugins();
		FileSystem.prepare();
	}

	static void exit() {
		System.exit(0);
	}

	public static Composite createTabControl(String title) {
		Composite tabControl = new Composite(tabs, 0);
		{
			tabControl.setLayout(new GridLayout());
			tabControl.setLayoutData(new GridData(FILL, FILL, true, false));
		}

		TabItem tab = addTabControl(tabControl, title);

		Button closeButton = new Button(tabControl, 0);
		{
			closeButton.setText("Close tab");
			closeButton.setLayoutData(new GridData(FILL, FILL, true, false));
			closeButton.addSelectionListener(new TabCloser(tab));
		}

		Composite result = new Composite(tabControl, 0);
		{
			result.setLayoutData(new GridData(FILL, FILL, true, true));
		}

		return result;
	}

	private static TabItem addTabControl(Composite newControl, String title) {
		TabItem tabItem = new TabItem(tabs, SWT.NONE);

		tabItem.setText(title);

		tabItem.setControl(newControl);

		return tabItem;
	}

	static void mainLoop() {
		while (true) {
			if (!display.readAndDispatch()) {
				while (!display.sleep()) {
				}
			}
		}
	}

	static void mainLoop(Widget widget) {
		boolean state = false;
		while (!widget.isDisposed()) {
			if (state) {
				if (display.sleep()) {
					state = false;
				}
			} else {
				if (!display.readAndDispatch()) {
					state = true;
				}
			}
		}
	}

	public static void waitForClose(Widget widget) {
		mainLoop(widget);
	}

	public static void error(final String message, final Throwable cause,
			final boolean wait) {
		if (currentThread() != display.getThread()) {
			Runnable r = new Runnable() {

				public void run() {
					error(message, cause, wait);
				}
			};
			if (wait) {
				display.syncExec(r);
			} else {
				display.asyncExec(r);
			}
			return;
		}
		StringWriter w = new StringWriter();
		PrintWriter pw = new PrintWriter(w);
		if (message != null) {
			pw.print(message);
			pw.println(':');
		}
		cause.printStackTrace(pw);
		pw.close();
		final Shell err = new Shell(display, TITLE | BORDER);
		err.setText(message != null ? message : "Error");
		Label text = new Label(err, 0);
		text.setText(w.toString());
		Button ok = new Button(err, PUSH);
		ok.setText("OK");
		ok.addSelectionListener(new SelectionAdapter() {

			public void widgetSelected(SelectionEvent e) {
				err.dispose();
			}
		});
		RowLayout layout = new RowLayout(VERTICAL);
		layout.fill = true;
		layout.wrap = false;
		err.setLayout(layout);
		err.setSize(err.computeSize(DEFAULT, DEFAULT));
		err.addShellListener(new ShellAdapter() {

			public void shellClosed(ShellEvent e) {
				e.doit = false;
			}
		});
		err.setVisible(true);
		if (wait) {
			mainLoop(err);
		}
	}

	public static void execute(Runnable runnable) {
		display.asyncExec(runnable);
	}

	private static Map<Runnable, Button> actions = new HashMap<Runnable, Button>();

	public static void registerAction(String name, Runnable action) {
		if (actions.containsKey(action)) {
			throw new IllegalArgumentException("Action is already registered");
		}
		Button button = new Button(panel, PUSH);
		button.setText(name);
		button.addSelectionListener(new ActionSelectionListener(name, action));
		actions.put(action, button);
	}

	public static void deregisterAction(Runnable action) {
		Button button = actions.get(action);
		if (button == null) {
			throw new IllegalArgumentException("Action is not registered");
		}
		button.dispose();
		actions.remove(action);
	}
}